//build:20240715
//This component is used to read cards, including M1 cards, PSAM cards, and the like.
//Dependencies: dxDriver, dxMap, dxLogger, dxDriver, dxCommon, dxEventBus
import { nfcClass } from './libvbar-p-dxnfc.so'
import dxCommon from './dxCommon.js'
import bus from './dxEventBus.js'
import dxMap from './dxMap.js'
const nfcObj = new nfcClass();
const map = dxMap.get("default")
const nfc = {}

/**
 * NFC Initialization
 * @param {number} useEid Optional: Whether to use a cloud certificate. 0: Do not use 1: Use
 * @param {number} type Optional, NFC type: 0 MCU, 1 Chip
 */
nfc.init = function (useEid = 0, type = 1) {
	let pointer = nfcObj.init(useEid, type)
	if (pointer === undefined || pointer === null) {
		throw new Error("nfc.init: init failed")
	}
	dxCommon.handleId("nfc", 'nfcid', pointer)
}

/**
 * NFC Standard Card Registration Callback
 */
nfc.cbRegister = function (callback) {
	let pointer = dxCommon.handleId("nfc", 'nfcid')
	return nfcObj.cbRegister(pointer, "nfc_cb", 1, callback)
}

/**
 * NFC PSAM card registration callback
 */
nfc.psamCbRegister = function (callback) {
	let pointer = dxCommon.handleId("nfc", 'nfcid')
	return nfcObj.nfcPsamCheckVgcardCallback(pointer, callback)
}

/**
 * NFC Cancel Initialization
 */
nfc.deinit = function () {
	let pointer = dxCommon.handleId("nfc", 'nfcid')
	let ret = nfcObj.cbUnregister(pointer, "nfc_cb")
	if (ret === false) {
		throw new Error("nfc.cbUnregister: cbUnregister failed")
	}
	return nfcObj.deinit(pointer)
}

/**
 * NFC card information creation
 * @param {number} cardType Card chip type (original manufacturer definition)
 * @param {ArrayBuffer} cardId card number
 * @param {number} type Card type (defined by us)
 * @returns cardInfo(pointer)
 */
nfc.cardInfoCreate = function (cardType, cardId, type) {
	if (!cardType) {
		throw new Error("cardInfoCreate:cardType should not be null or empty")
	}
	if (!cardId) {
		throw new Error("cardInfoCreate:cardId should not be null or empty")
	}
	if (!type) {
		throw new Error("cardInfoCreate:type should not be null or empty")
	}
	return nfcObj.cardInfoCreate(cardType, cardId, type);
}

/**
 * NFC card information destruction
 * @param {pointer} cardInfo Card information
 * @returns 
 */
nfc.cardInfoDestory = function (cardInfo) {
	if (!cardInfo) {
		throw new Error("cardInfoDestory:cardInfo should not be null or empty")
	}
	return nfcObj.cardInfoDestory(cardInfo);
}

/**
 * NFC card information copying
 * @param {pointer} cardInfo Card information
 * @returns cardInfo(pointer)
 */
nfc.cardInfoCopy = function (cardInfo) {
	if (cardInfo == null) {
		throw new Error("cardInfoCopy:cardInfo should not be null or empty")
	}
	return nfcObj.cardInfoCopy(cardInfo);
}

/**
 * NFC checks for card presence
 * @returns bool
 */
nfc.isCardIn = function () {
	let pointer = dxCommon.handleId("nfc", 'nfcid')
	return nfcObj.isCardIn(pointer);
}

/**
 * NFC reading M1 card sector
 * @param {number} taskFlg Mission marker:
 *                    0x00->AUTO This tells the scanner that the instruction can be executed independently, and there is no dependency between instructions.
 *                    0x01->START Inform the scanner that it has started operating on the card or that the card operation has not yet ended, and that there may be dependencies between the instructions.
 *                    0x02->FINISH Inform the scanner that this is the last instruction to operate the card, and restore the card's operating environment to its default state.
 * @param {number} secNum sector number
 * @param {number} logicBlkNum Block number (logical number 0~3 within the sector)
 * @param {number} blkNums Number of blocks
 * @param {array} key Key, 6 bytes in length
 * @param {number} keyType Key type: A: 0x60 B: 0x61
 * @returns Array read result undefined: failed.
 */
nfc.m1cardReadSector = function (taskFlg, secNum, logicBlkNum, blkNums, key, keyType) {
	let pointer = dxCommon.handleId("nfc", 'nfcid')
	_validate('m1cardReadSector', taskFlg, secNum, logicBlkNum, blkNums, key, keyType, ' ')
	return nfcObj.m1cardReadSector(pointer, taskFlg, secNum, logicBlkNum, blkNums, key, keyType);
}

/**
 * NFC reading M1 card sector
 * @param {number} taskFlg Mission marker:
 *                    0x00->AUTO This instruction informs the scanner that it can be executed independently, with no dependencies between instructions.
 *                    0x01->START Inform the scanner that it has started operating on the card or that the card operation has not yet ended, and that there may be dependencies between the instructions.
 *                    0x02->FINISH Inform the scanner that this is the last instruction to operate the card, and restore the card's operating environment to its default state.
 * @param {number} secNum sector number
 * @param {number} logicBlkNum Block number (logical number 0~3 within the sector)
 * @param {number} blkNums Number of blocks
 * @param {array} key Key, 6 bytes in length
 * @param {number} keyType Key type: A: 0x60 B: 0x61
 * @param {array} data Write data
 * @returns int write length -1: error
 */
nfc.m1cardWriteSector = function (taskFlg, secNum, logicBlkNum, blkNums, key, keyType, data) {
	let pointer = dxCommon.handleId("nfc", 'nfcid')
	_validate('m1cardWriteSector', taskFlg, secNum, logicBlkNum, blkNums, key, keyType, data)
	return nfcObj.m1cardWriteSector(pointer, taskFlg, secNum, logicBlkNum, blkNums, key, keyType, data);
}

/**
 * 
 * @param {number} taskFlg Mission marker:
 *                    0x00->AUTO This tells the scanner that the instruction can be executed independently, and there is no dependency between instructions.
 *                    0x01->START Inform the scanner that it has started operating on the card or that the card operation has not yet ended, and that there may be dependencies between the instructions.
 *                    0x02->FINISH Inform the scanner that this is the last instruction to operate the card, and restore the card's operating environment to its default state.
 * @param {number} blkNums Block number
 * @param {array} key Key, 6 bytes in length
 * @param {number} keyType Key type: A: 0x60 B: 0x61
 * @returns Array read result undefined: failed.
 */
nfc.m1cardReadBlk = function (taskFlg, blkNum, key, keyType) {
	let pointer = dxCommon.handleId("nfc", 'nfcid')
	_validate('m1cardReadBlk', taskFlg, 1, 0, blkNum, key, keyType, ' ')
	return nfcObj.m1cardReadBlk(pointer, taskFlg, blkNum, key, keyType);
}

/**
 * 
 * @param {number} taskFlg Mission marker:
 *                    0x00->AUTO This tells the scanner that the instruction can be executed independently, and there is no dependency between instructions.
 *                    0x01->START Inform the scanner that it has started operating on the card or that the card operation has not yet ended, and that there may be dependencies between the instructions.
 *                    0x02->FINISH Inform the scanner that this is the last instruction to operate the card, and restore the card's operating environment to its default state.
 * @param {number} blkNums Block number
 * @param {array} key Key, 6 bytes in length
 * @param {number} keyType Key type: A: 0x60 B: 0x61
 * @param {array} data Write data
 * @returns int write length -1: error
 */
nfc.m1cardWriteBlk = function (taskFlg, blkNum, key, keyType, data) {
	let pointer = dxCommon.handleId("nfc", 'nfcid')
	_validate('m1cardWriteBlk', taskFlg, 1, 0, blkNum, key, keyType, data)
	return nfcObj.m1cardWriteBlk(pointer, taskFlg, blkNum, key, keyType, data);
}

/**
 * Writing values ​​to the registers of the NFC module
 * @param {number} taskFlg Mission marker:
 *                    0x00->AUTO This tells the scanner that the instruction can be executed independently, and there is no dependency between instructions.
 *                    0x01->START Inform the scanner that it has started operating on the card or that the card operation has not yet ended, and that there may be dependencies between the instructions.
 *                    0x02->FINISH Inform the scanner that this is the last instruction to operate the card, and restore the card's operating environment to its default state.
 * @param {number} regAddr The register address to be written (refer to the corresponding manual if necessary).
 * @param {number} val The value to be written
 * @returns true/false
 */
nfc.nfcRegWrite = function (taskFlg, regAddr, val) {
	let pointer = dxCommon.handleId("nfc", 'nfcid')
	return nfcObj.nfcRegWrite(pointer, taskFlg, regAddr, val);
}

/**
 * Reading values ​​from the registers of the NFC module
 * @param {number} taskFlg Mission marker:
 *                    0x00->AUTO This tells the scanner that the instruction can be executed independently, and there is no dependency between instructions.
 *                    0x01->START Inform the scanner that it has started operating on the card or that the card operation has not yet ended, and that there may be dependencies between the instructions.
 *                    0x02->FINISH Inform the scanner that this is the last instruction to operate the card, and restore the card's operating environment to its default state.
 * @param {number} regAddr The address of the register to be read (refer to the corresponding manual if necessary).

 * @returns {number} The value read is null.
 */
nfc.nfcRegRead = function (taskFlg, regAddr) {
	let pointer = dxCommon.handleId("nfc", 'nfcid')
	return nfcObj.nfcRegRead(pointer, taskFlg, regAddr);
}

/**
 * ATS testing
 */
nfc.nfc_iso14443_type_a_get_ats = function () {
	let pointer = dxCommon.handleId("nfc", 'nfcid')
	return nfcObj.nfc_iso14443_type_a_get_ats(pointer)
}

/**
 * Card reactivation
 * @returns {boolean} true/false
 */
nfc.iso14443TypeaReactivate = function () {
	let pointer = dxCommon.handleId("nfc", 'nfcid')
	return nfcObj.iso14443TypeaReactivate(pointer)
}

/**
 * 
 * @param {number} taskFlg Mission marker:
 *                    0x00->AUTO This tells the scanner that the instruction can be executed independently, and there is no dependency between instructions.
 *                    0x01->START Inform the scanner that it has started operating on the card or that the card operation has not yet ended, and that there may be dependencies between the instructions.
 *                    0x02->FINISH Inform the scanner that this is the last instruction to operate the card, and restore the card's operating environment to its default state.
 * @param {ArrayBuffer} buffer 	Data to be sent
 * @param {number} bufferLen 	Length of data to be sent
 * @returns buffer
 */
nfc.iso14443Apdu = function (taskFlg, buffer, bufferLen) {
	let pointer = dxCommon.handleId("nfc", "nfcid")
	return nfcObj.iso14443Apdu(pointer, taskFlg, buffer, bufferLen);
}

/**
 * PSAM card power failure
 */
nfc.nfcPsamPowerDown = function () {
	let pointer = dxCommon.handleId("nfc", "nfcid")
	return nfcObj.nfcPsamPowerDown(pointer);
}

/**
 * NFC Change Status
 */
nfc.nfcPsamChangeBaud = function () {
	let pointer = dxCommon.handleId("nfc", "nfcid")
	return nfcObj.nfcPsamChangeBaud(pointer);
}

/**
 * PSAM card reset
 */
nfc.nfcPsamCardReset = function (force) {
	let pointer = dxCommon.handleId("nfc", "nfcid")
	return nfcObj.nfcPsamCardReset(pointer, force);
}

/**
 * Send PSAM APDU command
 */
nfc.nfcPsamCardApdu = function (buffer) {
	let pointer = dxCommon.handleId("nfc", "nfcid")
	return nfcObj.nfcPsamCardApdu(pointer, buffer);
}

/**
 * EID update cloud certificate configuration
 * @param {object} eidConfig Cloud Certificate Configuration		
 * 		@param {string} eidConfig.appid appid assigned to the application by the platform
 * 		@param {number} eidConfig.read_len; // Single card reading length, default 0x80
 * 		@param {number} eidConfig.declevel; // Whether to read photos: 1 for no reading, 2 for reading.
 * 		@param {number} eidConfig.loglevel; //Log levels, supporting 0, 1, 2
 * 		@param {number} eidConfig.model; // Whether to retrieve information directly: 
 			0 Yes, 1 No (i.e., 0 means return via the original path, returning identity information; 1 means forward, returning reqid).
 * 		@param {number} eidConfig.type; // Card types: 0 ID card; 1 electronic certificate
 * 		@param {number} eidConfig.pic_type; // Photo decoding data type: 0 wlt 1 jpg
 * 		@param {number} eidConfig.envCode; // Environmental Identification Code
 * 		@param {string} eidConfig.sn[128]; // Device serial number
 * 		@param {string} eidConfig.device_model[128]; // Equipment Model
 * 		@param {number} eidConfig.info_type; // Information return type: 0 - Identity information structure; 1 - Raw data (char)
 */
nfc.eidUpdateConfig = function (eidConfig) {
	if (eidConfig == null) {
		throw new Error("eidUpdateConfig:eidConfig should not be null or empty")
	}
	return nfcObj.eidUpdateConfig(eidConfig);
}

/**
 * Read the NTAG version number
 * 	@param {number} 		hdl               	NFC handle
 * 	@returns {ArrayBuffer} buffer
 */
nfc.nfcNtagReadVersion = function () {
	let pointer = dxCommon.handleId("nfc", 'nfcid')
	return nfcObj.nfcNtagReadVersion(pointer);
}

/**
 * Reading NTAG page content: 4 pages (16 bytes) are read in total.
 * 	@param {number} 		hdl               	NFC handle
 * 	@param {number} 		pageNum           	Starting page address:
 *                             						Read four pages each time
 *                             						If the address (Addr) is 04h, then return the contents of pages 04h, 05h, 06h, and 07h.
 * 	@returns {ArrayBuffer} buffer
 */
nfc.nfcNtagReadPage = function (pageNum) {
	let pointer = dxCommon.handleId("nfc", 'nfcid')
	if (pageNum == null) {
		throw new Error("nfcNtagReadPage:pageNum should not be null or empty")
	}
	return nfcObj.nfcNtagReadPage(pointer, pageNum);
}

/**
 * Reading multiple pages of NTAG content: The minimum buffer size for reading data is 4 times the number of pages; the length of the data to be read is 4 times the number of pages.
 * 	@param {number} 		hdl               	NFC handle
* 	@param {number} 		start_addr          Start page address
 * 	@param {number} 		end_addr            End page address         	
 * 	@returns {ArrayBuffer} buffer
 */
nfc.nfcNtagFastReadPage = function (start_page, end_page) {
	let pointer = dxCommon.handleId("nfc", 'nfcid')
	if (start_page == null) {
		throw new Error("nfcNtagFastReadPage:start_page should not be null or empty")
	}
	if (end_page == null) {
		throw new Error("nfcNtagFastReadPage:end_page should not be null or empty")
	}
	return nfcObj.nfcNtagFastReadPage(pointer, start_page, end_page);
}

/**
 * Write NTAG page content
 * 	@param {number} 		hdl               	NFC handle
 * 	@param {number} 		pageNum           	Page number written: Valid Addr parameter
 *                              				For NTAG213, page addresses 02h to 2Ch
 *                              				For NTAG215, page addresses 02h to 86h
 *                              				For NTAG216, page addresses 02h to E6h
 * 	@param {ArrayBuffer} 	pageData         	Content written to the page: four bytes
 * 	@returns {boolean} ture/false
 */
nfc.nfcNtagWritePage = function (pageNum, pageData) {
	let pointer = dxCommon.handleId("nfc", 'nfcid')
	if (pageNum == null) {
		throw new Error("nfcNtagWritePage:pageNum should not be null or empty")
	}
	if (!pageData) {
		throw new Error("nfcNtagWritePage:pageData should not be null or empty")
	}
	return nfcObj.nfcNtagWritePage(pointer, pageNum, pageData);
}

/**
 * Check if the NFC message queue is empty.
 * @returns bool
 */
nfc.msgIsEmpty = function () {
	let pointer = dxCommon.handleId("nfc", 'nfcid')
	return nfcObj.msgIsEmpty(pointer)
}

/**
 * Read data from the NFC message queue.
 * @returns JSON message object
 */
nfc.msgReceive = function () {
	let pointer = dxCommon.handleId("nfc", 'nfcid')
	let msg = nfcObj.msgReceive(pointer)
	return JSON.parse(msg);
}

function _validate(fun, taskFlg, secNum, logicBlkNum, blkNums, key, keyType, data) {
	if (![0x00, 0x01, 0x02].includes(taskFlg)) {
		throw new Error(fun, ":taskFlg error")
	}
	if (!(secNum >= 0)) {
		throw new Error(fun, ":secNum error")
	}
	if (logicBlkNum == null || logicBlkNum == undefined || logicBlkNum < 0 || logicBlkNum > 3) {
		throw new Error(fun, ":logicBlkNum error")
	}
	if (blkNums == null || blkNums == undefined || blkNums < 0 || blkNums > 59) {
		throw new Error(fun, ":blkNums error")
	}
	if (key == null || key === undefined || key.length < 0) {
		throw new Error(fun, ":key error")
	}
	if (![0x60, 0x61].includes(keyType)) {
		throw new Error(fun, ":keyType error")
	}
	if (data === null || data === undefined) {
		throw new Error(fun, ":data error")
	}
}

nfc.RECEIVE_MSG = '__nfc__MsgReceive'

/**
 * Simplify the use of NFC components; there's no need to poll for network status, as the network status is sent out via the event center.
 * The `run` command will only be executed once, and the basic network configuration cannot be modified after execution.
 * If you need to obtain card swipe data in real time, you can subscribe to the eventCenter event. The event topic is nfc.CARD, and the event content is similar to...
 * {id:'card ID', card_type:card chip type, id_len:card number length, type:card type, timestamp:'card swipe timestamp', monotonic_timestamp:'relative power-on time'}
 * @param {*} options 
 * 		@param {boolean} options.m1 Optional field, regular card callback switch
 * 		@param {boolean} options.psam Optional field: PSAM card callback switch
 */
nfc.run = function (options) {
	if (options === undefined || options.length === 0) {
		throw new Error("dxnfc.run:'options' parameter should not be null or empty")
	}
	let init = map.get("__nfc__run_init")
	if (!init) {//Ensure initialization only once
		map.put("__nfc__run_init", options)
		bus.newWorker("__nfc", '/app/code/dxmodules/nfcWorker.js')
	}
}

/**
 * If NFC uses a single thread, you can directly use the `run` function, which will automatically start a thread.
 * If you want to join another existing thread, you can use the following encapsulated function.
 */
nfc.worker = {
	//Before the while loop
	beforeLoop: function (options) {
		nfc.init(options.useEid)
		// PSAM and regular card callback
		if (options.m1) {
			nfc.cbRegister()
		}
		if (options.psam) {
			nfc.psamCbRegister()
		}
	},
	//In a while loop
	loop: function () {
		if (!nfc.msgIsEmpty()) {
			let res = nfc.msgReceive();
			bus.fire(nfc.RECEIVE_MSG, res)
		}
	}
}
export default nfc;
